<?php
/**
 * AMFDeserializer takes the raw amf input stream and converts it PHP objects
 * representing the data.
 * 
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 * @copyright (c) 2003 amfphp.org
 * @package flashservices
 * @subpackage io
 * @version $Id: AMFBaseDeserializer.php 90 2011-01-16 09:23:26Z nicolas.surian $
 */

/**
 * Required classes
 */ 
require_once(AMFPHP_BASE . "shared/util/MessageBody.php");
require_once(AMFPHP_BASE . "shared/util/MessageHeader.php");
require_once(AMFPHP_BASE . "amf/util/DateWrapper.php");

class AMFBaseDeserializer {
    /**
     * The raw data input
     * 
     * @access private 
     * @var string 
     */
    var $raw_data;

    /**
     * The current seek cursor of the stream
     * 
     * @access private 
     * @var int 
     */
    var $current_byte;

    /**
     * The length of the stream.  Since this class is not actually using a stream
     * the entire content of the stream is passed in as the initial argument so the
     * length can be determined.
     * 
     * @access private 
     * @var int 
     */
    var $content_length;
    
    /**
     * The number of headers in the packet.
     * 
     * @access private 
     * @var int 
     */
    var $header_count;

    /**
     * The content of the packet headers
     * 
     * @access private 
     * @var string 
     */
    var $headers;

    /**
     * The number of bodys in the packet.
     * 
     * @access private 
     * @var int 
     */
    var $body_count;

    /**
     * The content of the body elements
     * 
     * @access private 
     * @var string 
     */
    var $body;

    /**
     * The object to store the amf data.
     * 
     * @access private 
     * @var object 
     */
    var $amfdata;

    /**
     * The instance of the amfinput stream object
     * 
     * @access private 
     * @var object 
     */
    var $inputStream;
    
    /**
     * metaInfo
     */
    var $meta;
    
    var $storedStrings;
    var $storedObjects;
    var $storedDefinitions;
    var $amf0storedObjects;
    
    var $native;

    /**
     * Constructor method for the deserializer.  Constructing the deserializer converts the input stream
     * content to a AMFObject.
     * 
     * @param object $is The referenced input stream
     */
    function AMFBaseDeserializer($rd) {
        $this->isBigEndian = AMFPHP_BIG_ENDIAN;
        $this->current_byte = 0;
        $this->raw_data = $rd;  // store the stream in this object
        $this->content_length = strlen($this->raw_data); // grab the total length of this stream
        $this->charsetHandler = new CharsetHandler('flashtophp');
        $this->storedStrings = array();
        $this->storedObjects = array();
        $this->storedDefinitions = array();
        $this->native = $GLOBALS['amfphp']['native'] && function_exists('amf_decode');
        $this->decodeFlags = (AMFPHP_BIG_ENDIAN*2) | 4;
    } 

    /**
     * deserialize invokes this class to transform the raw data into valid object
     * 
     * @param object $amfdata The object to put the deserialized data in
     */
    function deserialize (&$amfdata) {
        $time = microtime_float();
        $this->amfdata = &$amfdata;
        $this->readHeader(); // read the binary header
        $this->readBody(); // read the binary body
        if($this->decodeFlags & 1 == 1)
        {
            //AMF3 mode
            $GLOBALS['amfphp']['encoding'] = "amf3";
        }
        global $amfphp;
        $amfphp['decodeTime'] = microtime_float() - $time;
    } 
    /**
     * returns the built AMFObject from the deserialization operation
     * 
     * @return object The deserialized AMFObject
     */
    function getAMFObject() {
        return $this->amfdata;
    } 
    
    /**
     * Decode callback is triggered when an object is encountered on decode
     */
    function decodeCallback($event, $arg)
    {
        if($event == 1) //Object
        {
           $type =$arg;
           return $this->mapClass($type);
        }
        else if($event == 2) //Object post decode
        {
           $obj = $arg;
           if(method_exists($obj, 'init'))
           {
               $obj->init();
           }
           return $obj;
        } 
        else if($event == 3) //XML post-decode
        {
            return $arg;
        }
        else if($event == 4) //Serializable post-decode
        {
            if($type == 'flex.messaging.io.ArrayCollection' || $type == 'flex.messaging.io.ObjectProxy')
            {
                return;
            }
            else
            {
               trigger_error("Unable to read externalizable data type " . $type, E_USER_ERROR);
               return "error";
            }
        }
        else if($event == 5) //ByteArray post decode
        {
            return new ByteArray($arg);
        }
    }

    /**
     * readHeader converts that header section of the amf message into php obects.
     * Header information typically contains meta data about the message.
     */
    function readHeader() {

        $topByte = $this->readByte(); // ignore the first two bytes --  version or something
        $secondByte = $this->readByte(); //0 for Flash,
                                         //1 for FlashComm                   
        //Disable debug events for FlashComm
        $GLOBALS['amfphp']['isFlashComm'] = $secondByte == 1;
        
        //If firstByte != 0, then the AMF data is corrupted, for example the transmission 
        //
        if(!($topByte == 0 || $topByte == 3))
        {
            trigger_error("Malformed AMF message, connection may have dropped");
            exit();
        }
        $this->header_count = $this->readInt(); //  find the total number of header elements
        while ($this->header_count--) { // loop over all of the header elements
            $name = $this->readUTF();
            $required = $this->readByte() == 1; // find the must understand flag
            //$length   = $this->readLong(); // grab the length of  the header element
            $this->current_byte += 4; // grab the length of the header element
            if($this->native)
            {
                $content = amf_decode($this->raw_data, $this->decodeFlags, $this->current_byte, array(& $this, "decodeCallback"));
            }
            else
            {
                $type = $this->readByte();  // grab the type of the element
                $content = $this->readData($type); // turn the element into real data
            }
            
            $this->amfdata->addHeader(new MessageHeader($name, $required, $content)); // save the name/value into the headers array
        }

    } 

    /**
     * readBody converts the payload of the message into php objects.
     */
    function readBody() {
        $this->body_count = $this->readInt(); // find the total number  of body elements
        while ($this->body_count--) { // loop over all of the body elements
            
            $this->amf0storedObjects = array();
            $this->storedStrings = array();
            $this->storedObjects = array();
            $this->storedDefinitions = array();
            
            $target = $this->readUTF();
            $response = $this->readUTF(); //    the response that the client understands
            
            //$length = $this->readLong(); // grab the length of    the body element
            $this->current_byte += 4;
            
            if($this->native)
               $data = amf_decode($this->raw_data, $this->decodeFlags, $this->current_byte, array(& $this, "decodeCallback"));
            else
            {
               $type = $this->readByte();  // grab the type of the element
               $data = $this->readData($type); // turn the element into real data
            }
            
            $this->amfdata->addBody(new MessageBody($target, $response, $data)); // add the body element to the body object
            
        } 
    } 
    
    /********************************************************************************
     *                       This used to be in AmfInputStream
     ********************************************************************************

    /**
     * readByte grabs the next byte from the data stream and returns it.
     * 
     * @return int The next byte converted into an integer
     */
    function readByte() {
        return ord($this->raw_data[$this->current_byte++]); // return the next byte
    }

    /**
     * readInt grabs the next 2 bytes and returns the next two bytes, shifted and combined
     * to produce the resulting integer
     * 
     * @return int The resulting integer from the next 2 bytes
     */
    function readInt() {
        return ((ord($this->raw_data[$this->current_byte++]) << 8) |
            ord($this->raw_data[$this->current_byte++])); // read the next 2 bytes, shift and add
    } 

    /**
     * readUTF first grabs the next 2 bytes which represent the string length.
     * Then it grabs the next (len) bytes of the resulting string.
     * 
     * @return string The utf8 decoded string
     */
    function readUTF() {
        $length = $this->readInt(); // get the length of the string (1st 2 bytes)
        //BUg fix:: if string is empty skip ahead
        if($length == 0)
        {
            return "";
        }
        else
        {
            $val = substr($this->raw_data, $this->current_byte, $length); // grab the string
            $this->current_byte += $length; // move the seek head to the end of the string
            return $this->charsetHandler->transliterate($val); // return the string
        }
    } 
    
    /**
     * readLong grabs the next 4 bytes shifts and combines them to produce an integer
     * 
     * @return int The resulting integer from the next 4 bytes
     */
    function readLong() {
        return ((ord($this->raw_data[$this->current_byte++]) << 24) |
            (ord($this->raw_data[$this->current_byte++]) << 16) |
            (ord($this->raw_data[$this->current_byte++]) << 8) |
            ord($this->raw_data[$this->current_byte++])); // read the next 4 bytes, shift and add
    } 

    /**
     * readDouble reads the floating point value from the bytes stream and properly orders
     * the bytes depending on the system architecture.
     * 
     * @return float The floating point value of the next 8 bytes
     */
    function readDouble() {
        $bytes = substr($this->raw_data, $this->current_byte, 8);
        $this->current_byte += 8;
        if ($this->isBigEndian) {
             $bytes = strrev($bytes);
        } 
        $zz = unpack("dflt", $bytes); // unpack the bytes
        return $zz['flt']; // return the number from the associative array
    } 

    /**
     * readLongUTF first grabs the next 4 bytes which represent the string length.
     * Then it grabs the next (len) bytes of the resulting in the string
     * 
     * @return string The utf8 decoded string
     */
    function readLongUTF() {
        $length = $this->readLong(); // get the length of the string (1st 4 bytes)
        $val = substr($this->raw_data, $this->current_byte, $length); // grab the string
        $this->current_byte += $length; // move the seek head to the end of the string
        return $this->charsetHandler->transliterate($val); // return the string
    } 
    
    function mapClass($typeIdentifier)
    {
        //Check out if class exists
        if($typeIdentifier == "")
        {
            return NULL;
        }
        $clazz = NULL;
        $mappedClass = str_replace('.', '/', $typeIdentifier);
        
        if($typeIdentifier == "flex.messaging.messages.CommandMessage")
        {
            return new CommandMessage();
        }
        if($typeIdentifier == "flex.messaging.messages.RemotingMessage")
        {
            return new RemotingMessage();
        }
        
        if(isset($GLOBALS['amfphp']['incomingClassMappings'][$typeIdentifier]))
        {
            $mappedClass = str_replace('.', '/', $GLOBALS['amfphp']['incomingClassMappings'][$typeIdentifier]);
        }
        
        $include = FALSE;
        if(file_exists($GLOBALS['amfphp']['customMappingsPath'] . $mappedClass . '.php'))
        {
            $include = $GLOBALS['amfphp']['customMappingsPath'] . $mappedClass . '.php';
        }
        elseif(file_exists($GLOBALS['amfphp']['customMappingsPath'] . $mappedClass . '.class.php'))
        {
            $include = $GLOBALS['amfphp']['customMappingsPath'] . $mappedClass . '.class.php';
        }
        
        if($include !== FALSE)
        {
            include_once($include);
            $lastPlace = strrpos('/' . $mappedClass, '/');
            $classname = substr($mappedClass, $lastPlace);
            if(class_exists($classname))
            {
                $clazz = new $classname;
            }
        }
        
        return $clazz; // return the object
    }
} 

?>